﻿// 文件使用MiniExcel源码， 使用时需遵循其 Apache License 2.0
// 修改摘要通知： 删除异步、Task等net2.0中不支持的内容，替换原先C#自带的zip处理方式，改用yacomp 进行解压缩。
using ExcelConfig.Attributes;
using ExcelConfig.helpers;
using ExcelConfig.OpenXml.Constants;
using ExcelConfig.Zip;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;
using yacomp.os;

namespace ExcelConfig.OpenXml.Styles
{
    internal class SheetStyleBuildContext : IDisposable
    {
        private static readonly string _emptyStylesXml = ExcelOpenXmlUtils.MinifyXml
        (@"
            <?xml version=""1.0"" encoding=""utf-8""?>
            <x:styleSheet xmlns:x=""http://schemas.openxmlformats.org/spreadsheetml/2006/main"">                
            </x:styleSheet>"
        );

        private readonly Dictionary<string, ZipPackageInfo> _zipDictionary;
        private readonly MiniExcelZipArchive _archive;
        private readonly Encoding _encoding;
        private readonly ICollection<ExcelColumnAttribute> _columns;

        private StringReader _emptyStylesXmlStringReader;
        private YacompFile _oldStyleXmlZipEntry;
        private Stream _oldXmlReaderStream;
        private Stream _newXmlWriterStream;
        
        private bool _initialized;
        private bool _finalized;
        private bool _disposed;

        public SheetStyleBuildContext(Dictionary<string, ZipPackageInfo> zipDictionary, MiniExcelZipArchive archive, Encoding encoding, ICollection<ExcelColumnAttribute> columns)
        {
            _zipDictionary = zipDictionary;
            _archive = archive;
            _encoding = encoding;
            _columns = columns;
        }

        public XmlReader OldXmlReader { get; private set; }
        public XmlWriter NewXmlWriter { get; private set; }
        public SheetStyleElementInfos OldElementInfos { get; private set; }
        public SheetStyleElementInfos GenerateElementInfos { get; private set; }
        public IList<ExcelColumnAttribute> ColumnsToApply { get; private set; }
        public int CustomFormatCount { get; private set; }
        public string path { get; internal set; }

        public void Initialize(SheetStyleElementInfos generateElementInfos)
        {
            if (_initialized)
                throw new InvalidOperationException("The context has been initialized.");

            path = ExcelFileNames.Styles;
            if (_archive.storage.Exist(ExcelFileNames.Styles))
            {
                _oldStyleXmlZipEntry =_archive.storage.FindFile(ExcelFileNames.Styles);
                using (var oldStyleXmlStream = _oldStyleXmlZipEntry.ToStream())
                {
                    OldElementInfos = ReadSheetStyleElementInfos(XmlReader.Create(oldStyleXmlStream, new XmlReaderSettings { IgnoreWhitespace = true }));
                }

                _oldXmlReaderStream = _oldStyleXmlZipEntry.ToStream();
                OldXmlReader = XmlReader.Create(_oldXmlReaderStream, new XmlReaderSettings { IgnoreWhitespace = true });

                path += ".temp";
            }
            else
            {
                OldElementInfos = new SheetStyleElementInfos();

                _emptyStylesXmlStringReader = new StringReader(_emptyStylesXml);
                OldXmlReader = XmlReader.Create(_emptyStylesXmlStringReader, new XmlReaderSettings { IgnoreWhitespace = true });
            }

            _newXmlWriterStream = new MemoryStream();
            NewXmlWriter = XmlWriter.Create(_newXmlWriterStream, new XmlWriterSettings { Indent = true, Encoding = _encoding });

            GenerateElementInfos = generateElementInfos;
            ColumnsToApply = helper.to_list(SheetStyleBuilderHelper.GenerateStyleIds(OldElementInfos.CellXfCount + generateElementInfos.CellXfCount, _columns).GetEnumerator());//这里暂时加ToArray，避免多次计算，如果有性能问题再考虑优化
            CustomFormatCount = ColumnsToApply.Count;

            _initialized = true;
        }
        public void FinalizeAndUpdateZipDictionary()
        {
            if (!_initialized)
                throw new InvalidOperationException("The context has not been initialized.");
            if (_disposed)
                throw new ObjectDisposedException(nameof(SheetStyleBuildContext));
            if (_finalized)
                throw new InvalidOperationException("The context has been finalized.");
            
            try
            {
                OldXmlReader.Close();
                OldXmlReader = null;
                _oldXmlReaderStream?.Dispose();
                _oldXmlReaderStream = null;
                
                _emptyStylesXmlStringReader?.Dispose();
                _emptyStylesXmlStringReader = null;

                NewXmlWriter.Flush();
                NewXmlWriter = null;

                if (_oldStyleXmlZipEntry != null)
                {
                    _archive.storage.OperateFile(_oldStyleXmlZipEntry.FullPath).Delete();
                    _oldStyleXmlZipEntry = null;
                    var finalStyleXmlZipEntry = _archive.storage.AddFilebyPath(ExcelFileNames.Styles, _newXmlWriterStream);

                    _zipDictionary[ExcelFileNames.Styles] = new ZipPackageInfo(finalStyleXmlZipEntry, ExcelContentTypes.Styles);
                }

                _finalized = true;
            }
            catch (Exception ex)
            {
                throw new Exception("Failed to finalize and replace styles.", ex);
            }
        }
        private static SheetStyleElementInfos ReadSheetStyleElementInfos(XmlReader reader)
        {
            var elementInfos = new SheetStyleElementInfos();
            while (reader.Read())
            {
                SetElementInfos(reader, elementInfos);
            }
            return elementInfos;
        }
        private static void SetElementInfos(XmlReader reader, SheetStyleElementInfos elementInfos)
        {
            if (reader.NodeType != XmlNodeType.Element)
                return;
            
            switch (reader.LocalName)
            {
                case "numFmts":
                    elementInfos.ExistsNumFmts = true;
                    elementInfos.NumFmtCount = GetCount();
                    break;
                case "fonts":
                    elementInfos.ExistsFonts = true;
                    elementInfos.FontCount = GetCount();
                    break;
                case "fills":
                    elementInfos.ExistsFills = true;
                    elementInfos.FillCount = GetCount();
                    break;
                case "borders":
                    elementInfos.ExistsBorders = true;
                    elementInfos.BorderCount = GetCount();
                    break;
                case "cellStyleXfs":
                    elementInfos.ExistsCellStyleXfs = true;
                    elementInfos.CellStyleXfCount = GetCount();
                    break;
                case "cellXfs":
                    elementInfos.ExistsCellXfs = true;
                    elementInfos.CellXfCount = GetCount();
                    break;
            }

            int GetCount()
            {
                var count = reader.GetAttribute("count") ?? string.Empty;
                return int.TryParse(count, out var countValue) ? countValue : 0;
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (_disposed)
                return;
            
            if (disposing)
            {
                OldXmlReader?.Close();
                _oldXmlReaderStream?.Dispose();
                _emptyStylesXmlStringReader?.Dispose();

                NewXmlWriter?.Close();
            }

            _disposed = true;
        }
    }
}
